package com.gitee.huanminabc.utils_tools.redis.redisson.utils;

/**
 * 简要描述
 *  1.支持自旋等待
 *  2.支持重复加锁
 *  3.支持超时时间
 *  4.默认支持可重入锁,但是需要注意,如果是可重入锁,那么在解锁的时候,需要解锁和加锁的次数一样,否则会导致锁无法释放
 *
 * @Author: huanmin
 * @Date: 2022/8/1 17:39
 * @Version: 1.0
 * @Description: 文件作用详细描述....
 */

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class RedissonLockUtil {

    //从配置类中获取redisson对象
    @Autowired
    private RedissonUtil redissonUtil;
    private final String LOCK_TITLE = "redisLock_";
    private final ThreadLocal<Integer> count = new ThreadLocal<>();//计数

    /**
     * 5秒
     */
    public static final long DEFAULT_WAIT_TIME = 5L;

    /**
     * 30秒
     */
    public static final long DEFAULT_LEAVE_TIME = 30L;

    /**
     *  //一直等待获取锁
     * @param lockKey 锁的key
     * @param leaseTime 锁的过期时间,单位秒 ,意思就是说如果在leaseTime时间内没有释放锁,那么锁会自动释放
     * @param getLockHandle 获取锁后的处理
     */
    public void lock(String lockKey,long leaseTime,Runnable getLockHandle) {
        RLock lock = redissonUtil.getRedissonClient().getLock((LOCK_TITLE + lockKey));
        try {
            lock.lock(leaseTime, TimeUnit.SECONDS);
            getLockHandle.run();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 尝试获取锁,如果获取不到锁,那么就返回false,如果获取到锁,那么就返回true,一旦获取到锁,必须手动释放锁,不会自动释放
     * @param lockKey 锁的key
     * @param getLockHandle 获取锁后的处理
     * @param noLockHandle 未获取锁的处理
     */
    public boolean tryLock(String lockKey,Runnable getLockHandle) {
        RLock lock =  redissonUtil.getRedissonClient().getLock((LOCK_TITLE + lockKey));
        boolean b = lock.tryLock();
        if (b) {
            try {
                getLockHandle.run();
                return true;
            } finally {
                lock.unlock();
            }
        } else {
            log.warn("{}获取锁失败", lockKey);
            return false;
        }
    }
    //加锁 , 线程加锁的时候发现,锁有人用了 ,那么就会进入自旋等待

    /**
     *
     * 多功能加锁,可以设置超时时间,设置是否自璇,可以设置自旋时间,可以设置自旋次数
     * @param lockKey 锁的key
     * @param seconds 锁的过期时间,单位秒 ,意思就是说如果在seconds时间内没有释放锁,那么锁会自动释放,-1表示永不过期
     * @param repeatLockCount 重复加锁的次数 ,-1表示无限次数直到获取锁成功,0表示不重复加锁,1表示加锁一次,2表示加锁两次
     * @param lockWaitTimeMs 每次加锁的等待时间 ,间隔时间
     * @param getLockHandle 获取锁的回调
     */
    @SneakyThrows
    public boolean stuntLock(String lockKey, long seconds, int repeatLockCount, int lockWaitTimeMs,Runnable getLockHandle) {
        count.set(0); //初始化值
        try {
            //获取锁对象
            RLock mylock =  redissonUtil.getRedissonClient().getLock((LOCK_TITLE + lockKey));
            mylock.lock();
            do {
                //尝试加锁
                boolean b = mylock.tryLock(0, seconds, TimeUnit.SECONDS);;
                if (b) {
                    try {
                        getLockHandle.run();
                        return true;
                    }finally {
                        mylock.unlock();
                    }
                }
                log.warn("尝试加锁{}失败",lockKey);
                if (repeatLockCount!=0){
                    Thread.sleep(lockWaitTimeMs);//等待一段时间,再次尝试获取锁
                    //加锁次数增加
                    count.set(count.get() + 1);
                }
                //获取加锁的次数,如果是-1那么持续加锁,如果满足加锁次数那么结束加锁 ,如果是0那么不加锁
            } while (repeatLockCount == -1 || count.get() < repeatLockCount);
            log.warn("尝试加锁{}失败已尝试了:{}次", lockKey, count.get());
            return false;
        } finally {
            //清除线程变量
            count.remove();
        }
    }

    //手动加锁, 加锁方式自行控制 返回的RLock对象 里面有2个方法,一个是lock() 一个是tryLock() 可以根据自己的需求选择
    public RLock lock(String lockKey) {
        return redissonUtil.getRedissonClient().getLock((LOCK_TITLE + lockKey));
    }

    //手动释放锁,如果在事物中,那么就在事物提交后释放锁,避免锁的释放时间过早导致事物数据不一致
    public void release(String lockKey) {
        //如果不在事物中那么直接释放锁
        if (!TransactionSynchronizationManager.isActualTransactionActive()) {
            __release(lockKey);
        } else {
            //如果在事物中那么就在事物提交后释放锁
            try {
                TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                    @Override
                    public void afterCommit() {
                        __release(lockKey);
                    }
                });
            } catch (Exception e) {
                // 任何异常都释放锁
                __release(lockKey);
            }
        }
    }
    //手动锁的释放
    private void __release(String lockKey) {
        //获取所对象
        RLock mylock =  redissonUtil.getRedissonClient().getLock((LOCK_TITLE + lockKey));
        // 这里判断下当前key是否上锁，不然业务执行时间大于锁自动释放时间后，解锁报异常
        if (mylock.isLocked()&&mylock.isHeldByCurrentThread()) { // 是否还是锁定状态并且锁是当前线程的
                mylock.unlock(); // 释放锁
                log.info("解锁:{}", lockKey);
        }else{
            log.warn("{}解锁失败,锁不存在或者锁不是当前线程持有",lockKey);
        }
    }


    //分布式环境执行锁
    /**
     * 在分布式锁中执行
     *
     * @param waitTime  锁等着时间 如果在waitTime时间内没有获取到锁,那么就会返回
     * @param leaveTime 最大锁持有时间 如果在leaveTime时间内没有执行完毕,那么锁会自动释放
     * @param timeUnit  时间单位
     * @param lockKey   锁的key
     * @param task      任务对象
     */
    public boolean tryLock(long waitTime, long leaveTime, TimeUnit timeUnit, String lockKey, Runnable task) {
        //判断leaveTime是否小于waitTime,如果小于那么就抛出异常
        if (leaveTime < waitTime) {
            throw new RuntimeException("最大持有时间不能小于等待时间,否则会导致锁过早释放,其他任务获取到锁了");
        }
        RLock lock = redissonUtil.getRedissonClient().getLock(lockKey);
        try {
            boolean b = lock.tryLock(waitTime, leaveTime, timeUnit);
            if (b) {
                task.run();
            } else {
                log.warn("获取锁失败");
                return false;
            }
        } catch (InterruptedException e) {
            log.error("获取锁失败", e);
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
        }
        return true;
    }

    /**
     * 在分布式锁中执行 - 使用默认时间,等待时间5秒,最大持有时间30秒 ,就算有延迟5秒也够了
     *
     * @param lockKey 锁的key
     * @param task    任务对象
     */
    public boolean defaultTryLock(String lockKey, Runnable task) {
      return   tryLock(DEFAULT_WAIT_TIME, DEFAULT_LEAVE_TIME, TimeUnit.SECONDS, lockKey, task);
    }
}
